package jsr292.java.lang.invoke;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;

import jsr292.weaver.Capabilities;
import jsr292.weaver.Debug;
import jsr292.weaver.opt.OptimizerContext;
import jsr292.weaver.opt.OptimizerSnippets;
import jsr292.weaver.opt.OptimizerVarAllocator;

class MHLookupUnreflectMethod extends MethodHandle {
    private final Method method;
    private final boolean isStatic;

    public MHLookupUnreflectMethod(Method method, MethodType methodType, boolean isStatic) {
        super(methodType);
        this.method=method;
        this.isStatic=isStatic;
    }
    
    private Object invoke(Object target, Object... arguments) throws Throwable {
        if (!isStatic && target == null)
            throw null;
        
        try {
            return method.invoke(target, arguments);
        } catch (IllegalAccessException e) {
            throw (LinkageError)new LinkageError().initCause(e);
        } catch (InvocationTargetException e) {
            Throwable cause=e.getCause();
            if (cause instanceof RuntimeException) {
                throw (RuntimeException)cause;
            }
            if (cause instanceof Error) {
                throw (Error)cause;
            }
            throw cause;
            
        } catch(IllegalArgumentException e) {
          //XXX debug
          throw new IllegalArgumentException(
        	e.getMessage() + ' '+ Arrays.toString(arguments)+ " types "+ Arrays.toString(method.getParameterTypes()),
        	e);
        }
    }
    
    @Override
    public Object $invoke$() throws Throwable {
        assert isStatic;
        return invoke(null, (Object[])null);
    }
    
    @Override
    public Object $invoke$(Object o1) throws Throwable {
        if (isStatic)
            return invoke(null, o1);
        return invoke(o1, (Object[])null);
    }
    
    @Override
    public Object $invoke$(Object o1, Object o2) throws Throwable {
        if (isStatic)
            return invoke(null, o1, o2);
        return invoke(o1, o2);
    }
    
    @Override
    public Object $invoke$(Object o1, Object o2, Object o3) throws Throwable {
        if (isStatic)
            return invoke(null, o1, o2, o3);
        return invoke(o1, o2, o3);
    }
    
    @Override
    public Object $invoke$(Object o1, Object o2, Object o3, Object o4) throws Throwable {
        if (isStatic)
            return invoke(null, o1, o2, o3, o4);
        return invoke(o1, o2, o3, o4);
    }
    
    @Override
    public Object $invoke$(Object o1, Object o2, Object o3, Object o4, Object o5) throws Throwable {
        if (isStatic)
            return invoke(null, o1, o2, o3, o4, o5);
        return invoke(o1, o2, o3, o4, o5);
    }
    
    @Override
    public Object $invoke$(Object o1, Object o2, Object o3, Object o4, Object o5, Object o6) throws Throwable {
        if (isStatic)
            return invoke(null, o1, o2, o3, o4, o5, o6);
        return invoke(o1, o2, o3, o4, o5, o6);
    }
    
    @Override
    public Object $invoke$(Object o1, Object o2, Object o3, Object o4, Object o5, Object o6, Object o7) throws Throwable {
        if (isStatic)
            return invoke(null, o1, o2, o3, o4, o5, o6, o7);
        return invoke(o1, o2, o3, o4, o5, o6, o7);
    }
    
    @Override
    public Object $invoke$(Object... rest) throws Throwable {
        if (isStatic)
            return invoke(null, rest);
        return invoke(rest[0], ArrayUtils.copyOfRange(rest, 1, rest.length));
    }
    
    @Override
    public boolean isWeavable(Class<?> classBeingRedefined) {
        if (!Access.isVisible(classBeingRedefined, method) && !Capabilities.canDefine()) {
            if (Debug.DEBUG) System.err.println("unreflect method: can't access method "+method+" and can't define");
            return false;
        }
        return true;
    }
    @Override
    public void weave(OptimizerContext context, OptimizerVarAllocator allocator) {
        if (Debug.DEBUG) {
            Debug.checkAllocator(allocator, this);
        }
        OptimizerSnippets.unreflectMethod(context, allocator, method, isStatic);
    }
}
